var Options = function () {

    this.lightDirection_X = -0.22;
    this.lightDirection_Y = -0.61;
    this.lightDirection_Z = -0.77;

    this.currentAngleX = 66;
    this.currentAngleY = 0;
    this.currentAngleZ = 0;

    this.eyeX = 0;
    this.eyeY = 0;
    this.eyeZ = 6;

    this.limit = 30;

    this.lightPos_X = 0.6;
    this.lightPos_Y = 0.6;
    this.lightPos_Z = 1;

    this.innerLimit = 38;

    this.outerLimit = 42;

};

const gui = new dat.GUI();
var options = new Options();


gui.add(options, 'lightDirection_X', -1, 1).step(0.01);
gui.add(options, 'lightDirection_Y', -1, 1).step(0.01);
gui.add(options, 'lightDirection_Z', -1, 1).step(0.01);

gui.add(options, 'currentAngleX', 0, 360).step(1);
gui.add(options, 'currentAngleY', 0, 360).step(1);
gui.add(options, 'currentAngleZ', 0, 360).step(1);

gui.add(options, 'eyeX', -10, 10).step(1);
gui.add(options, 'eyeY', -10, 10).step(1);
gui.add(options, 'eyeZ', 5, 15).step(1);

gui.add(options, 'lightPos_X', -1, 1).step(0.1);
gui.add(options, 'lightPos_Y', -1, 1).step(0.1);
gui.add(options, 'lightPos_Z', -1, 2).step(0.1);

gui.add(options, 'limit', 0, 100).step(0.01);

gui.add(options, 'innerLimit', 0, 50).step(0.01);
gui.add(options, 'outerLimit', 0, 50).step(0.01);

var myReq;

var VSHADER_SOURCE =
    `
    attribute vec4 a_Position;
    uniform mat4 u_MvpMatrix;
    
    void main() {
        gl_Position = u_MvpMatrix * a_Position;
    }
    `

var FSHADER_SOURCE =
    `
    #ifdef GL_ES
    precision mediump float;
    #endif
    void main() {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    `;

var VSHADER_SOURCE_PERFRAG =
    `
    attribute vec4 a_Position;
    attribute vec4 a_Normal;
    attribute vec2 a_TexCoord;
    uniform mat4 u_MvpMatrix;
    uniform mat4 u_ModelMatrix;
    uniform mat4 u_NormalMatrix;
    uniform vec3 u_lightWorldPosition;
    uniform vec3 u_viewWorldPosition;
    varying vec4 v_Color;
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec2 v_TexCoord;
    varying vec3 v_surfaceToView;
    varying vec3 v_surfaceToLight;
    void main()
    {
        vec4
        color = vec4(1.0, 1.0, 1.0, 1.0);
        gl_Position = u_MvpMatrix * a_Position;
        v_TexCoord = a_TexCoord;
        v_Position = vec3(u_ModelMatrix * a_Position);
        v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));
        v_Color = color;
        v_surfaceToView = u_viewWorldPosition - v_Position;
        v_surfaceToLight = u_lightWorldPosition - v_Position;
    }
`
var FSHADER_SOURCE_PERFRAG =
    `#ifdef GL_ES
    precision mediump float;
    #endif
    uniform sampler2D u_Sampler;
    uniform float u_shininess;
    uniform vec3 u_lightDirection;
    uniform float u_limit;
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec4 v_Color;
    varying vec2 v_TexCoord;
    varying vec3 v_surfaceToView;
    varying vec3 v_surfaceToLight;
    
    uniform float u_innerLimit;          // in dot space
    uniform float u_outerLimit;          // in dot space
    void main() {
      vec3 normal = normalize(v_Normal);

      vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
      vec3 surfaceToViewDirection = normalize(v_surfaceToView);
      vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
    
      // float light = 0.0;
      // float specular = 0.0;
      // float dotFromDirection = dot(surfaceToLightDirection, -u_lightDirection);
      // if (dotFromDirection >= u_limit) {
      //   light = dot(normal, surfaceToLightDirection);
      //   if (light > 0.0) {
      //     specular = pow(dot(normal, halfVector), u_shininess);
      //   }
      // }
      
      u_limit;
      
      float dotFromDirection = dot(surfaceToLightDirection,
                               -u_lightDirection);
      // float inLight = step(u_limit, dotFromDirection);
      // float light = inLight * dot(normal, surfaceToLightDirection);
      // float specular = inLight * pow(dot(normal, halfVector), u_shininess);
      
      float limitRange = u_innerLimit - u_outerLimit;
      float inLight = clamp((dotFromDirection - u_outerLimit) / limitRange, 0.0, 1.0);
      float light = inLight * dot(normal, surfaceToLightDirection);
      float specular = inLight * pow(dot(normal, halfVector), u_shininess);
      
      vec4 texColor = texture2D(u_Sampler, v_TexCoord);
      gl_FragColor = texColor;
      gl_FragColor.rgb *= light;
      gl_FragColor.rgb += specular;
    }
      `
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);

var program_light = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
var program_cube = createProgram(gl, VSHADER_SOURCE_PERFRAG, FSHADER_SOURCE_PERFRAG);

function main() {
    function step(timestamp) {
        draw(gl, canvas);
        myReq = window.requestAnimationFrame(step);
    }

    myReq = window.requestAnimationFrame(step)
}

function draw(gl, canvas) {
    drawCube(gl, canvas)
    drawLightCube(gl, canvas);
}

function drawCube(gl, canvas) {
    gl.useProgram(program_cube);
    gl.program = program_cube;

    if (!initTextures(gl)) {
        console.log('Failed to intialize the texture.');
        return;
    }

    var n = initVertexBuffersCube(gl);

    gl.clearColor(0, 0, 0, 1);
    gl.enable(gl.DEPTH_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');

    var lightWorldPositionLocation = gl.getUniformLocation(gl.program, "u_lightWorldPosition");
    var viewWorldPositionLocation = gl.getUniformLocation(gl.program, "u_viewWorldPosition");

    var shininessLocation = gl.getUniformLocation(gl.program, "u_shininess");
    var lightDirectionLocation = gl.getUniformLocation(gl.program, "u_lightDirection");
    var limitLocation = gl.getUniformLocation(gl.program, "u_limit");
    var innerLimitLocation = gl.getUniformLocation(gl.program, "u_innerLimit");
    var outerLimitLocation = gl.getUniformLocation(gl.program, "u_outerLimit");

    let lightDirection_X = options.lightDirection_X;
    let lightDirection_Y = options.lightDirection_Y;
    let lightDirection_Z = options.lightDirection_Z;

    let currentAngleX = options.currentAngleX;
    let currentAngleY = options.currentAngleY;
    let currentAngleZ = options.currentAngleZ;

    let eyeX = options.eyeX;
    let eyeY = options.eyeY;
    let eyeZ = options.eyeZ;

    let lightPos_X = options.lightPos_X;
    let lightPos_Y = options.lightPos_Y;
    let lightPos_Z = options.lightPos_Z;

    let limit = options.limit;
    let innerLimit = options.innerLimit;
    let outerLimit = options.outerLimit;

    var modelMatrix = new Matrix4();
    var mvpMatrix = new Matrix4();
    var normalMatrix = new Matrix4();

    modelMatrix.scale(0.5, 0.5, 0.5).rotate(currentAngleX, 1, 0, 0).rotate(currentAngleY, 0, 1, 0).rotate(currentAngleZ, 0, 0, 1);

    mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    mvpMatrix.lookAt(eyeX, eyeY, eyeZ, 0, 0, 0, 0, 1, 0);
    mvpMatrix.multiply(modelMatrix);

    normalMatrix.setInverseOf(modelMatrix);
    normalMatrix.transpose();

    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);
    gl.uniform3fv(viewWorldPositionLocation, [eyeX, eyeY, eyeZ]);
    gl.uniform3fv(lightWorldPositionLocation, [lightPos_X, lightPos_Y, lightPos_Z]);

    gl.uniform3fv(lightDirectionLocation, [lightDirection_X, lightDirection_Y, lightDirection_Z]);
    gl.uniform1f(limitLocation, Math.cos(limit * Math.PI / 180));
    gl.uniform1f(shininessLocation, 50);
    gl.uniform1f(innerLimitLocation, Math.cos(innerLimit * Math.PI / 180));
    gl.uniform1f(outerLimitLocation, Math.cos(outerLimit * Math.PI / 180));
    // console.log(Math.cos(limit * Math.PI / 180))
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function drawLightCube(gl, canvas, type) {
    gl.useProgram(program_light);
    gl.program = program_light;

    var n = initVertexBuffersLightCube(gl);

    var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');

    let lightPos_X = options.lightPos_X;
    let lightPos_Y = options.lightPos_Y;
    let lightPos_Z = options.lightPos_Z;

    let eyeX = options.eyeX;
    let eyeY = options.eyeY;
    let eyeZ = options.eyeZ;

    var modelMatrix = new Matrix4();
    var mvpMatrix = new Matrix4();
    var normalMatrix = new Matrix4();

    modelMatrix.translate(lightPos_X, lightPos_Y, lightPos_Z).scale(0.05, 0.05, 0.05);

    mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    mvpMatrix.lookAt(eyeX, eyeY, eyeZ, 0, 0, 0, 0, 1, 0);
    mvpMatrix.multiply(modelMatrix);

    normalMatrix.setInverseOf(modelMatrix);
    normalMatrix.transpose();

    gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initTextures(gl) {
    var texture = gl.createTexture();
    if (!texture) {
        console.log('Failed to create the texture object');
        return false;
    }

    var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
    if (!u_Sampler) {
        console.log('Failed to get the storage location of u_Sampler');
        return false;
    }
    loadTexture(gl, texture, u_Sampler, image);
    return true;
}

function loadTexture(gl, texture, u_Sampler, image) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    gl.uniform1i(u_Sampler, 0);
}

function initVertexBuffersCube(gl) {
    var vertices = new Float32Array([
        1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
        1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
        1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0,
        -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,
        -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
        1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0
    ]);

    var normals = new Float32Array([
        0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
        1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
        -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
        0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,
        0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0
    ]);

    var verticesTexCoords = new Float32Array([
        // Vertex coordinates, texture coordinate
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
        0.0, 0.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0,
    ]);

    var indices = new Uint8Array([
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        8, 9, 10, 8, 10, 11,
        12, 13, 14, 12, 14, 15,
        16, 17, 18, 16, 18, 19,
        20, 21, 22, 20, 22, 23
    ]);

    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;
    // if (!initArrayBuffer(gl, 'a_Color', colors, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_TexCoord', verticesTexCoords, 2, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return false;
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    return indices.length;
}

function initVertexBuffersLightCube(gl) {
    var vertices = new Float32Array([
        1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
        1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0,
        1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0,
        -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0,
        -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
        1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0
    ]);

    var indices = new Uint8Array([
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        8, 9, 10, 8, 10, 11,
        12, 13, 14, 12, 14, 15,
        16, 17, 18, 16, 18, 19,
        20, 21, 22, 20, 22, 23
    ]);

    if (!initArrayBuffer(gl, 'a_Position', vertices, 3, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return false;
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

    return indices.length;
}

function initVertexBuffersSphere(gl) {
    var SPHERE_DIV = 13;

    var i, ai, si, ci;
    var j, aj, sj, cj;
    var p1, p2;

    var positions = [];
    var indices = [];

    for (j = 0; j <= SPHERE_DIV; j++) {
        aj = j * Math.PI / SPHERE_DIV;
        sj = Math.sin(aj);
        cj = Math.cos(aj);
        for (i = 0; i <= SPHERE_DIV; i++) {
            ai = i * 2 * Math.PI / SPHERE_DIV;
            si = Math.sin(ai);
            ci = Math.cos(ai);

            positions.push(si * sj);  // X
            positions.push(cj);       // Y
            positions.push(ci * sj);  // Z
        }
    }

    for (j = 0; j < SPHERE_DIV; j++) {
        for (i = 0; i < SPHERE_DIV; i++) {
            p1 = j * (SPHERE_DIV + 1) + i;
            p2 = p1 + (SPHERE_DIV + 1);

            indices.push(p1);
            indices.push(p2);
            indices.push(p1 + 1);

            indices.push(p1 + 1);
            indices.push(p2);
            indices.push(p2 + 1);
        }
    }

    if (!initArrayBuffer(gl, 'a_Position', new Float32Array(positions), 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer(gl, 'a_Normal', new Float32Array(positions), 3, gl.FLOAT)) return -1;

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    var indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
        console.log('Failed to create the buffer object');
        return -1;
    }
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

    return indices.length;
}

function initArrayBuffer(gl, attribute, data, num, type) {
    var buffer = gl.createBuffer();
    if (!buffer) {
        console.log('Failed to create the buffer object');
        return false;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    var a_attribute = gl.getAttribLocation(gl.program, attribute);
    if (a_attribute < 0) {
        console.log('Failed to get the storage location of ' + attribute);
        return false;
    }
    gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
    gl.enableVertexAttribArray(a_attribute);

    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    return true;
}

var image = new Image();
image.onload = function () {
    main();
};
image.src = './sky.jpg';


